﻿using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;

namespace Mars.GpuInstance
{
    [BurstCompile(FloatPrecision = FloatPrecision.Low, FloatMode = FloatMode.Fast)]
    public struct CullingJob : IJobParallelFor
    {
        [NativeDisableContainerSafetyRestriction] [ReadOnly]
        public NativeList<float4x4> input;

        [NativeDisableContainerSafetyRestriction] [ReadOnly]
        public NativeArray<float4> cameraPlanes;

        [ReadOnly] public float3 boxCenter;
        [ReadOnly] public float3 boxExtents;
        [ReadOnly] public float3 cameraPos;
        [ReadOnly] public int maximumLODLevel;
        [ReadOnly] public bool forceRender;

        [NativeDisableContainerSafetyRestriction] [ReadOnly]
        public NativeList<RefSimpleBound> cullBounds;

        [ReadOnly] public NativeList<int3> speceShowInfo;

        [NativeDisableContainerSafetyRestriction]
        public NativeArray<bool> viewIndex; //可视序号
        
        [NativeDisableContainerSafetyRestriction]
        public NativeArray<int> lodInfo; //lod层级

        [NativeDisableContainerSafetyRestriction]
        public NativeArray<int> lodRange; //lod层级范围

        public void Execute(int index)
        {
            var instance = input[index];
            var showInfo = speceShowInfo[index];
            var speceCullingDis = showInfo.x;
            var ignoreCullBound = showInfo.y == 1;
            var show = showInfo.z == 1;
            if (!show)
            {
                viewIndex[index] = false;
                return;
            }

            float3 pos = new float3(instance.c3.x, instance.c3.y, instance.c3.z);
            bool isCull = CalculationLod(index, speceCullingDis, pos);
            if (!ignoreCullBound)
            {
                if (!isCull)
                {
                    for (int i = 0; i < cullBounds.Length; i++)
                    {
                        var simpleBound = cullBounds[i].bound;
                        isCull = MathUtil.IsIntersect(simpleBound.minPos, simpleBound.maxPos, pos.x, pos.z);
                        if (isCull)
                        {
                            break;
                        }
                    }
                }
            }

            if (!isCull)
            {
                float3 boundMin = pos + boxCenter - boxExtents;
                float3 boundMax = pos + boxCenter + boxExtents;

                var boundVerts = new NativeArray<float3>(8, Allocator.Temp);
                boundVerts[0] = boundMin;
                boundVerts[1] = boundMax;
                boundVerts[2] = new float3(boundMax.x, boundMax.y, boundMin.z);
                boundVerts[3] = new float3(boundMax.x, boundMin.y, boundMax.z);
                boundVerts[6] = new float3(boundMax.x, boundMin.y, boundMin.z);
                boundVerts[4] = new float3(boundMin.x, boundMax.y, boundMax.z);
                boundVerts[5] = new float3(boundMin.x, boundMax.y, boundMin.z);
                boundVerts[7] = new float3(boundMin.x, boundMin.y, boundMax.z);
                isCull = CullingUtil.IsCulled(boundVerts, cameraPlanes);
                boundVerts.Dispose();
            }

            // 剔除
            if (!isCull)
            {
                viewIndex[index] = true;
            }
            else
            {
                viewIndex[index] = false;
            }
        }
        
        
        bool CalculationLod(int index, int specCullingDis, float3 pos)
        {
            var dis = math.distance(pos, cameraPos);
            if (specCullingDis > 0 && dis > specCullingDis) //超过指定的裁剪距离
            {
                return true;
            }

            bool isLodCull = true;
            var len = lodRange.Length;
            for (int i = 0; i < len; i++)
            {
                if (i >= maximumLODLevel)
                {
                    if (dis < lodRange[i])
                    {
                        lodInfo[index] = i;
                        isLodCull = false;
                        break;
                    }
                }
            }

            if (isLodCull && forceRender)
            {
                isLodCull = false;
                lodInfo[index] = len - 1;
            }

            return isLodCull;
        }
    }
}